using System;
using System.Collections.Generic;
    using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.Drawing.Drawing2D;

namespace Lyxite.CatKingdom
{
    enum Actions
    {
        MoveUp,
        MoveDown,
        MoveLeft,
        MoveRight,

        MoveUpLeft,
        MoveUpRight,
        MoveDownLeft,
        MoveDownRight,

        DivideUp,
        DivideDown,
        DivideLeft,
        DivideRight,

        DivideUpLeft,
        DivideUpRight,
        DivideDownLeft,
        DivideDownRight,

        LeftClick,
        RightClick,
        MiddleClick,

        Enlarge,
        Shrink,

        Clear,

        Toggle
    }

    class KeyListener
    {
        public event EventHandler EnabledChanged;
        
        static Dictionary<Keys, Actions> bindedKeys = new Dictionary<Keys, Actions>();

        static KeyListener()
        {
            bindedKeys.Add(Keys.Decimal, Actions.Toggle);

            bindedKeys.Add(Keys.NumPad0, Actions.Clear);

            bindedKeys.Add(Keys.NumPad1, Actions.DivideDownLeft);
            bindedKeys.Add(Keys.NumPad3, Actions.DivideDownRight);
            bindedKeys.Add(Keys.NumPad7, Actions.DivideUpLeft);
            bindedKeys.Add(Keys.NumPad9, Actions.DivideUpRight);
            
            bindedKeys.Add(Keys.NumPad2, Actions.MoveDown);
            bindedKeys.Add(Keys.NumPad4, Actions.MoveLeft);
            bindedKeys.Add(Keys.NumPad6, Actions.MoveRight);
            bindedKeys.Add(Keys.NumPad8, Actions.MoveUp);

            bindedKeys.Add(Keys.NumPad5, Actions.LeftClick);
            bindedKeys.Add(Keys.Divide, Actions.MiddleClick);
            bindedKeys.Add(Keys.Multiply, Actions.RightClick);

            bindedKeys.Add(Keys.Add, Actions.Enlarge);
            bindedKeys.Add(Keys.Subtract, Actions.Shrink);
        }

        MouseSimulator cat = new MouseSimulator();

        bool enabled = true;

        private void Toggle()
        {
            Enabled = !enabled;
            
            //if (enabled)
            //{
            //    UpdateCursor();
            //}

            //Draw();
        }

        public void KeyDown(object sender, KeyEventArgs e)
        {
            Keys key = e.KeyCode;
            if (!enabled && !(bindedKeys.ContainsKey(key) && bindedKeys[key] == Actions.Toggle))
                return;

            bool handled = false;

            if (bindedKeys.ContainsKey(key))
            {
                Actions action = bindedKeys[key];
                if(action == Actions.Toggle)
                    Toggle();
                else
                    cat.Process(action, false);
                handled = true;
            }

            e.Handled = handled;
        }

        public void KeyUp(object sender, KeyEventArgs e)
        {
            Keys key = e.KeyCode;
            if (!enabled && !(bindedKeys.ContainsKey(key) && bindedKeys[key] == Actions.Toggle))
                return;

            if (bindedKeys.ContainsKey(key))
            {
                Actions action = bindedKeys[key];
                if (action != Actions.Toggle)
                    cat.Process(action, true);
                e.Handled = true;
            }
        }

        public void KeyPress(object sender, KeyPressEventArgs e)
        {
        }

        public bool Enabled
        {
            get
            {
                return enabled;
            }
            set
            {
                enabled = value;
                if (EnabledChanged != null)
                    EnabledChanged(this, new EventArgs());
            }
        }
    }

    class MouseSimulator
    {
        const int MINIMUN_SIZE = 2;

        readonly Brush brush = new SolidBrush(Color.FromArgb(50, Color.Blue));
        readonly Pen pen = new Pen(Color.FromArgb(100, Color.Red));
        readonly Rectangle screen = Screen.PrimaryScreen.Bounds;
        readonly RectangleF screenF;

        Bitmap oldbuffer;
        RectangleF oldTile;
        RectangleF tile;
        bool hasTile = false;

        public MouseSimulator()
        {
            screenF = new RectangleF(screen.X, screen.Y, screen.Width, screen.Height);
        }

        public void Process(Actions actions, bool isUp)
        {
            switch (actions)
            {
                case Actions.LeftClick:
                case Actions.RightClick:
                case Actions.MiddleClick:
                    PerformClick(actions, isUp);
                    break;
                case Actions.DivideLeft:
                case Actions.DivideRight:
                case Actions.DivideUp:
                case Actions.DivideDown:
                case Actions.DivideUpLeft:
                case Actions.DivideUpRight:
                case Actions.DivideDownLeft:
                case Actions.DivideDownRight:
                    DivideTile(actions,isUp);
                    break;
                case Actions.MoveLeft:
                case Actions.MoveRight:
                case Actions.MoveUp:
                case Actions.MoveDown:
                case Actions.MoveUpLeft:
                case Actions.MoveUpRight:
                case Actions.MoveDownLeft:
                case Actions.MoveDownRight:
                    MoveTile(actions,isUp);
                    break;
                case Actions.Enlarge:
                case Actions.Shrink:
                    ResizeTile(actions,isUp);
                    break;
                case Actions.Clear:
                    ClearTile();
                    break;
            }
        }


        private void ClearTile()
        {
            hasTile = false;
            Draw();
            oldbuffer = null;
        }

        private void PerformClick(Actions actions, bool isUp)
        {
            if (isUp)
            {
                switch (actions)
                {
                    case Actions.LeftClick:
                        WinAPI.mouse_event((uint)WinAPI.MouseEventFlags.LEFTUP, 0, 0, 0, 0);
                        break;
                    case Actions.RightClick:
                        WinAPI.mouse_event((uint)WinAPI.MouseEventFlags.RIGHTUP, 0, 0, 0, 0);
                        break;
                    case Actions.MiddleClick:
                        WinAPI.mouse_event((uint)WinAPI.MouseEventFlags.MIDDLEUP, 0, 0, 0, 0);
                        break;
                    default:
                        throw new ArgumentException();
                }
            }
            else
            {
                switch (actions)
                {
                    case Actions.LeftClick:
                        WinAPI.mouse_event((uint)WinAPI.MouseEventFlags.LEFTDOWN, 0, 0, 0, 0);
                        break;
                    case Actions.RightClick:
                        WinAPI.mouse_event((uint)WinAPI.MouseEventFlags.RIGHTDOWN, 0, 0, 0, 0);
                        break;
                    case Actions.MiddleClick:
                        WinAPI.mouse_event((uint)WinAPI.MouseEventFlags.MIDDLEDOWN, 0, 0, 0, 0);
                        break;
                    default:
                        throw new ArgumentException();
                }
            }
        }

        private bool ValidateRectangle(ref RectangleF newTile, ref RectangleF target)
        {
            if (newTile.Width < MINIMUN_SIZE || newTile.Height < MINIMUN_SIZE)
            {
                return false;
            }
            else if (screenF.IntersectsWith(newTile) || screenF.Contains(newTile))
            {
                if (newTile.Width > screen.Width)
                {
                    newTile.X = 0;
                    newTile.Width = screen.Width;
                }
                if (newTile.Height > screen.Height)
                {
                    newTile.Y = 0;
                    newTile.Height = screen.Height;
                }

                if (target.Equals(newTile))
                    return false;
                else
                {
                    target = newTile;
                    return true;
                }
            }
            else
            {
                return false;
            }
        }

        private void ResizeTile(Actions action, bool isUp)
        {
            if (isUp) return;

            if (!hasTile)
            {
                tile = screen;
            }

            RectangleF newTile;

            switch (action)
            {
                case Actions.Shrink:
                    newTile = new RectangleF(tile.X + tile.Width / 4, tile.Y + tile.Height / 4, tile.Width / 2, tile.Height / 2);
                    break;
                case Actions.Enlarge:
                    newTile = new RectangleF(tile.X - tile.Width / 2, tile.Y - tile.Height / 2, tile.Width * 2, tile.Height * 2);
                    break;
                default:
                    throw new ArgumentException();
            }

            hasTile = true;
            if (ValidateRectangle(ref newTile, ref tile))
            {
                Draw();
                UpdateCursor();
            }
        }

        private void MoveTile(Actions acition, bool isUp)
        {
            if (isUp)
                return;

            if (!hasTile)
            {
                tile = screen;
            }

            RectangleF newTile;
            switch (acition)
            {
                case Actions.MoveDown:
                    newTile = new RectangleF(tile.X, tile.Y + tile.Height / 2, tile.Width, tile.Height);
                    break;
                case Actions.MoveLeft:
                    newTile = new RectangleF(tile.X - tile.Width / 2, tile.Y, tile.Width, tile.Height);
                    break;
                case Actions.MoveRight:
                    newTile = new RectangleF(tile.X + tile.Width / 2, tile.Y, tile.Width, tile.Height);
                    break;
                case Actions.MoveUp:
                    newTile = new RectangleF(tile.X, tile.Y - tile.Height / 2, tile.Width, tile.Height);
                    break;
                default:
                    throw new ArgumentException();
            }
            hasTile = true;
            if (ValidateRectangle(ref newTile, ref tile))
            {
                Draw();
                UpdateCursor();
            }
        }

        private void DivideTile(Actions action, bool isUp)
        {
            if (isUp) return;

            if (!hasTile)
            {
                tile = screen;
            }

            RectangleF newTile;
            switch (action)
            {
                case Actions.DivideUpLeft:
                    newTile = new RectangleF(tile.X, tile.Y, tile.Width / 2, tile.Height / 2);
                    break;
                case Actions.DivideUpRight:
                    newTile = new RectangleF(tile.X + tile.Width - tile.Width / 2, tile.Y, tile.Width / 2, tile.Height / 2);
                    break;
                case Actions.DivideDownLeft:
                    newTile = new RectangleF(tile.X, tile.Y + tile.Height - tile.Height / 2, tile.Width / 2, tile.Height / 2);
                    break;
                case Actions.DivideDownRight:
                    newTile = new RectangleF(tile.X + tile.Width - tile.Width / 2, tile.Y + tile.Height - tile.Height / 2, tile.Width / 2, tile.Height / 2);
                    break;
                default:
                    throw new ArgumentException();
            }

            hasTile = true;
            if (ValidateRectangle(ref newTile, ref tile))
            {
                Draw();
                UpdateCursor();
            }
        }

        /// <summary>
        /// Round
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        int _R(double number)
        {
            return (int)Math.Round(number);
        }

        private void Draw()
        {
            IntPtr dc = WinAPI.GetDC(IntPtr.Zero);
            Graphics g = Graphics.FromHdc(dc);

            // clear old tile
            if (oldbuffer != null)
                g.DrawImage(oldbuffer, _R(oldTile.X), _R(oldTile.Y), _R(oldTile.Width), _R(oldTile.Height));

            if (hasTile)
            {
                // copy screen
                Rectangle tileInt = new Rectangle(_R(tile.X), _R(tile.Y), _R(tile.Width), _R(tile.Height));

                using (Bitmap buffer = new Bitmap(tileInt.Width, tileInt.Height))
                {
                    Graphics gbuf = Graphics.FromImage(buffer);
                    gbuf.CopyFromScreen(tileInt.X, tileInt.Y, 0, 0, tileInt.Size);

                    // make a copy
                    Bitmap buffer2 = new Bitmap(buffer);

                    // change the buffer
                    Rectangle zeroRect = new Rectangle(0, 0, tileInt.Width, tileInt.Height);
                    //gbuf.FillRectangle(new LinearGradientBrush(zeroRect,
                    //        Color.FromArgb(100, Color.Blue), Color.FromArgb(50, Color.Blue), 45
                    //    ), 
                    //    zeroRect);
                    gbuf.FillRectangle(brush, zeroRect);

                    gbuf.DrawRectangle(pen, 0, 0, tileInt.Width - 1, tileInt.Height - 1);

                    // draw the buffer to screen
                    g.DrawImage(buffer, tileInt);

                    if (oldbuffer != null)
                        oldbuffer.Dispose();
                    oldbuffer = buffer2;
                    oldTile = tile;
                }
            }

            WinAPI.ReleaseDC(IntPtr.Zero, dc);
        }

        private void UpdateCursor()
        {
            if (hasTile)
            {
                WinAPI.SetCursorPos(_R(tile.X + tile.Width / 2), _R(tile.Y + tile.Height / 2));
            }
        }
    }
}
